﻿//******************************************************************************************************
//  LegacyAdapterCollectionBase.cs - Gbtc
//
//  Copyright © 2010, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/eclipse-1.0.php
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  2/2013 - University of Illinois, Information Trust Institute, Coordinated Science Laboratory
//       Updated code to work for the SIEGate system and architecture
//
//  09/02/2010 - J. Ritchie Carroll
//       Generated original version of source code.
//
//******************************************************************************************************

using System.Threading;
using GSF;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;

namespace SIEGate.Core.Legacy
{
    /// <summary>
    /// Represents a collection of <see cref="IAdapter"/> implementations.
    /// </summary>
    /// <typeparam name="T">Type of <see cref="IAdapter"/> this collection contains.</typeparam>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public abstract class LegacyAdapterCollectionBase<T> : Collection<T>, ILegacyAdapterCollection where T : IAdapter
    {
        // Todo: put these in the right places
        // Stubs for IAdapter - most of these will never be used in a collection, except DataPublisher, so we can ignore
        // proper implementation for now
        public MeasurementPasser Emit {get; set;}
        public ISet<Guid> IncomingStreamIDs { get; set; }
        public ISet<Guid> OutgoingStreamIDs { get; set; }
        public IAdapterInfo Info { get; set; }
        public virtual void Collect(params IMeasurement[] m)
        {
            throw new NotImplementedException("LegacyAdapterCollectionBase should never have its Collect method called.");
        }
        public virtual void Link(IAdapter other)
        {
            throw new NotImplementedException("LegacyAdapterCollectionBase should never have its link method called.");
        }
        #region [ Members ]

        // Fields
        private string m_name;
        private uint m_id;
        private string m_connectionString;
        private Dictionary<string, string> m_settings;
        private DataSet m_dataSource;
        private string m_dataMember;
        private string[] m_inputSourceIDs;
        private string[] m_outputSourceIDs;
        private MeasurementKey[] m_requestedInputMeasurementKeys;
        private MeasurementKey[] m_requestedOutputMeasurementKeys;
        private bool m_disposed;

        // Hack: for legcy stuff that depends on old style behavior and hasn't started integrating disruptor
        private bool m_useLegacyMeasurements;

        #endregion

        #region [ Constructors ]

        /// <summary>
        /// Constructs a new instance of the <see cref="LegacyAdapterCollectionBase{T}"/>.
        /// </summary>
        protected LegacyAdapterCollectionBase()
        {
            m_name = GetType().Name;
            m_settings = new Dictionary<string, string>();
        }

        /// <summary>
        /// Releases the unmanaged resources before the <see cref="LegacyAdapterCollectionBase{T}"/> object is reclaimed by <see cref="GC"/>.
        /// </summary>
        ~LegacyAdapterCollectionBase()
        {
            Dispose(false);
        }

        #endregion

        #region [ Properties ]

        // Hack: for legcy stuff that depends on old style behavior and hasn't started integrating disruptor
        public bool UseLegacyCommunication
        {
            get { return m_useLegacyMeasurements; }
            set { m_useLegacyMeasurements = value; }
        }

        /// <summary>
        /// Gets or sets the name of this <see cref="LegacyAdapterCollectionBase{T}"/>.
        /// </summary>
        public virtual string Name
        {
            get
            {
                return m_name;
            }
            set
            {
                m_name = value;
            }
        }

        /// <summary>
        /// Gets or sets numeric ID associated with this <see cref="LegacyAdapterCollectionBase{T}"/>.
        /// </summary>
        public virtual uint ID
        {
            get
            {
                return m_id;
            }
            set
            {
                m_id = value;
            }
        }

        /// <summary>
        /// Gets or sets key/value pair connection information specific to this <see cref="LegacyAdapterCollectionBase{T}"/>.
        /// </summary>
        public virtual string ConnectionString
        {
            get
            {
                return m_connectionString;
            }
            set
            {
                m_connectionString = value;

                // Preparse settings upon connection string assignment
                if (string.IsNullOrWhiteSpace(m_connectionString))
                    m_settings = new Dictionary<string, string>();
                else
                    m_settings = m_connectionString.ParseKeyValuePairs();
            }
        }

        /// <summary>
        /// Gets or sets <see cref="DataSet"/> based data source used to load each <see cref="IAdapter"/>.
        /// Updates to this property will cascade to all items in this <see cref="LegacyAdapterCollectionBase{T}"/>.
        /// </summary>
        /// <remarks>
        /// Table name specified in <see cref="DataMember"/> from <see cref="DataSource"/> is expected
        /// to have the following table column names:<br/>
        /// ID, AdapterName, AssemblyName, TypeName, ConnectionString<br/>
        /// ID column type should be integer based, all other column types are expected to be string based.
        /// </remarks>
        public virtual DataSet DataSource
        {
            get
            {
                return m_dataSource;
            }
            set
            {
                m_dataSource = value;

                // Update data source for items in this collection
                lock (this)
                {
                    foreach (T item in this)
                    {
                        item.Info.ExtractConfiguration(m_dataSource);
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets specific data member (e.g., table name) in <see cref="DataSource"/> used to <see cref="Initialize"/> this <see cref="LegacyAdapterCollectionBase{T}"/>.
        /// </summary>
        /// <remarks>
        /// Table name specified in <see cref="DataMember"/> from <see cref="DataSource"/> is expected
        /// to have the following table column names:<br/>
        /// ID, AdapterName, AssemblyName, TypeName, ConnectionString<br/>
        /// ID column type should be integer based, all other column types are expected to be string based.
        /// </remarks>
        public virtual string DataMember
        {
            get
            {
                return m_dataMember;
            }
            set
            {
                m_dataMember = value;
            }
        }

        /// <summary>
        /// Gets or sets <see cref="MeasurementKey.Source"/> values used to filter input measurement keys.
        /// </summary>
        /// <remarks>
        /// The collection classes simply track this value if assigned, no automatic action is taken.
        /// </remarks>
        public virtual string[] InputSourceIDs
        {
            get
            {
                return m_inputSourceIDs;
            }
            set
            {
                m_inputSourceIDs = value;
            }
        }

        /// <summary>
        /// Gets or sets <see cref="MeasurementKey.Source"/> values used to filter output measurements.
        /// </summary>
        /// <remarks>
        /// The collection classes simply track this value if assigned, no automatic action is taken.
        /// </remarks>
        public virtual string[] OutputSourceIDs
        {
            get
            {
                return m_outputSourceIDs;
            }
            set
            {
                m_outputSourceIDs = value;
            }
        }

        /// <summary>
        /// Gets or sets input measurement keys that are requested by other adapters based on what adapter says it can provide.
        /// </summary>
        public virtual MeasurementKey[] RequestedInputMeasurementKeys
        {
            get
            {
                // If a specific set of input measurement keys has been assigned, use that set
                if (m_requestedInputMeasurementKeys != null)
                    return m_requestedInputMeasurementKeys;

                // Otherwise return cumulative results of all child adapters
                lock (this)
                {
                    MeasurementKey[] keys = this.Cast<IAdapter>()
                        .SelectMany(item =>
                        {
                            if (item is ILegacyActionAdapter)
                                return ((ILegacyActionAdapter)item).RequestedInputMeasurementKeys;

                            if (item is ILegacyOutputAdapter)
                                return ((ILegacyOutputAdapter)item).RequestedInputMeasurementKeys;

                            return new MeasurementKey[0];
                        })
                        .ToArray();

                    if (keys.Length > 0)
                        return keys;
                }

                return null;
            }
            set
            {
                m_requestedInputMeasurementKeys = value;
            }
        }

        /// <summary>
        /// Gets or sets output measurement keys that are requested by other adapters based on what adapter says it can provide.
        /// </summary>
        public virtual MeasurementKey[] RequestedOutputMeasurementKeys
        {
            get
            {
                // If a specific set of output measurement keys has been assigned, use that set
                if (m_requestedOutputMeasurementKeys != null)
                    return m_requestedOutputMeasurementKeys;

                // Otherwise return cumulative results of all child adapters
                lock (this)
                {
                    MeasurementKey[] keys = this.Cast<IAdapter>()
                        .SelectMany(item =>
                        {
                            if (item is ILegacyInputAdapter)
                                return ((ILegacyInputAdapter)item).RequestedOutputMeasurementKeys;

                            if (item is ILegacyActionAdapter)
                                return ((ILegacyActionAdapter)item).RequestedOutputMeasurementKeys;

                            return new MeasurementKey[0];
                        })
                        .ToArray();

                    if (keys.Length > 0)
                        return keys;
                }

                return null;
            }
            set
            {
                m_requestedOutputMeasurementKeys = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the <see cref="LegacyAdapterCollectionBase{T}"/> is read-only.
        /// </summary>
        public virtual bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Gets flag that detemines if <see cref="IAdapter"/> implementations are automatically initialized
        /// when they are added to the collection.
        /// </summary>
        protected virtual bool AutoInitialize
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Gets settings <see cref="Dictionary{TKey,TValue}"/> parsed when <see cref="ConnectionString"/> was assigned.
        /// </summary>
        public Dictionary<string, string> Settings
        {
            get
            {
                return m_settings;
            }
            set
            {
                m_settings = value;
            }
        }

        /// <summary>
        /// Gets the descriptive status of this <see cref="LegacyAdapterCollectionBase{T}"/>.
        /// </summary>
        public virtual string Status
        {
            get
            {
                StringBuilder status = new StringBuilder();
                DataSet dataSource = this.DataSource;

                // Show collection status
                status.AppendFormat("  Total adapter components: {0}", Count);
                status.AppendLine();
                status.AppendFormat("       Data source defined: {0}", (dataSource != null));
                status.AppendLine();
                if (dataSource != null)
                {
                    status.AppendFormat("    Referenced data source: {0}, {1} tables", dataSource.DataSetName, dataSource.Tables.Count);
                    status.AppendLine();
                }
                status.AppendFormat("    Data source table name: {0}", DataMember);
                status.AppendLine();

                Dictionary<string, string> keyValuePairs = Settings;
                char[] keyChars;
                string value;

                status.AppendFormat("         Connection string: {0} key/value pairs", keyValuePairs.Count);
                //                            1         2         3         4         5         6         7
                //                   123456789012345678901234567890123456789012345678901234567890123456789012345678
                //                                         Key = Value
                //                                                        1         2         3         4         5
                //                                               12345678901234567890123456789012345678901234567890
                status.AppendLine();
                status.AppendLine();

                foreach (KeyValuePair<string, string> item in keyValuePairs)
                {
                    keyChars = item.Key.Trim().ToCharArray();
                    keyChars[0] = char.ToUpper(keyChars[0]);

                    value = item.Value.Trim();
                    if (value.Length > 50)
                        value = value.TruncateRight(47) + "...";

                    status.AppendFormat("{0} = {1}", (new string(keyChars)).TruncateRight(25).PadLeft(25), value.PadRight(50));
                    status.AppendLine();
                }

                status.AppendLine();

                if (Count > 0)
                {
                    int index = 0;

                    status.AppendLine();
                    status.AppendFormat("Status of each {0} component:", Name);
                    status.AppendLine();
                    status.Append(new string('-', 79));
                    status.AppendLine();

                    // Show the status of registered components.
                    lock (this)
                    {
                        foreach (T item in this)
                        {
                            IProvideStatus statusProvider = item as IProvideStatus;

                            if (statusProvider != null)
                            {
                                // This component provides status information.                       
                                status.AppendLine();
                                status.AppendFormat("Status of {0} component {1}, {2}:", typeof(T).Name, ++index, statusProvider.Name);
                                status.AppendLine();
                                try
                                {
                                    status.Append(statusProvider.Status);
                                }
                                catch (Exception ex)
                                {
                                    status.AppendFormat("Failed to retrieve status due to exception: {0}", ex.Message);
                                    status.AppendLine();
                                }
                            }
                        }
                    }

                    status.AppendLine();
                    status.Append(new string('-', 79));
                    status.AppendLine();
                }

                return status.ToString();
            }
        }

        #endregion

        #region [ Methods ]

        /// <summary>
        /// Releases all the resources used by the <see cref="LegacyAdapterCollectionBase{T}"/> object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="LegacyAdapterCollectionBase{T}"/> object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!m_disposed)
            {
                try
                {
                    if (disposing)
                    {
                        Clear();        // This disposes all items in collection...
                    }
                }
                finally
                {
                    m_disposed = true;  // Prevent duplicate dispose.
                }
            }
        }

        /// <summary>
        /// Attempts to get the adapter with the specified <paramref name="id"/>.
        /// </summary>
        /// <param name="id">ID of adapter to get.</param>
        /// <param name="adapter">Adapter reference if found; otherwise null.</param>
        /// <returns><c>true</c> if adapter with the specified <paramref name="id"/> was found; otherwise <c>false</c>.</returns>
        public virtual bool TryGetAdapterByID(uint id, out T adapter)
        {
            return TryGetAdapter<uint>(id, (item, value) => item.Info.ID == value, out adapter);
        }

        /// <summary>
        /// Attempts to get the adapter with the specified <paramref name="name"/>.
        /// </summary>
        /// <param name="name">Name of adapter to get.</param>
        /// <param name="adapter">Adapter reference if found; otherwise null.</param>
        /// <returns><c>true</c> if adapter with the specified <paramref name="name"/> was found; otherwise <c>false</c>.</returns>
        public virtual bool TryGetAdapterByName(string name, out T adapter)
        {
            return TryGetAdapter<string>(name, (item, value) => string.Compare(item.Info.Name, value, true) == 0, out adapter);
        }

        /// <summary>
        /// Attempts to get the adapter with the specified <paramref name="value"/> given <paramref name="testItem"/> function.
        /// </summary>
        /// <param name="value">Value of adapter to get.</param>
        /// <param name="testItem">Function delegate used to test item <paramref name="value"/>.</param>
        /// <param name="adapter">Adapter reference if found; otherwise null.</param>
        /// <returns><c>true</c> if adapter with the specified <paramref name="value"/> was found; otherwise <c>false</c>.</returns>
        protected virtual bool TryGetAdapter<TValue>(TValue value, Func<T, TValue, bool> testItem, out T adapter)
        {
            lock (this)
            {
                foreach (T item in this)
                {
                    if (!testItem(item, value))
                        continue;
                    adapter = item;
                    return true;
                }
            }

            adapter = default(T);
            return false;
        }

        // Explicit IAdapter implementation of TryGetAdapterByID
        bool ILegacyAdapterCollection.TryGetAdapterByID(uint id, out IAdapter adapter)
        {
            T adapterT;
            bool result = TryGetAdapterByID(id, out adapterT);
            adapter = adapterT as IAdapter;
            return result;
        }

        // Explicit IAdapter implementation of TryGetAdapterByName
        bool ILegacyAdapterCollection.TryGetAdapterByName(string name, out IAdapter adapter)
        {
            T adapterT;
            bool result = TryGetAdapterByName(name, out adapterT);
            adapter = adapterT as IAdapter;
            return result;
        }

        /// <summary>
        /// Gets a short one-line status of this <see cref="LegacyAdapterBase"/>.
        /// </summary>
        /// <param name="maxLength">Maximum number of available characters for display.</param>
        /// <returns>A short one-line summary of the current status of this <see cref="LegacyAdapterBase"/>.</returns>
        public virtual string GetShortStatus(int maxLength)
        {
            return string.Format("Total components: {0:N0}", Count).CenterText(maxLength);
        }

        /// <summary>
        /// This method does not wait for <see cref="LegacyAdapterCollectionBase{T}"/>.
        /// </summary>
        /// <param name="timeout">This parameter is ignored.</param>
        /// <returns><c>true</c> for <see cref="LegacyAdapterCollectionBase{T}"/>.</returns>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public virtual bool WaitForInitialize(int timeout)
        {
            // Adapter collections have no need to wait
            return true;
        }

        /// <summary>
        /// Removes all elements from the <see cref="Collection{T}"/>.
        /// </summary>
        protected override void ClearItems()
        {
            // Dispose each item before clearing the collection
            lock (this)
            {
                foreach (T item in this)
                {
                    DisposeItem(item);
                }

                base.ClearItems();
            }
        }

        /// <summary>
        /// Inserts an element into the <see cref="Collection{T}"/> the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        /// <param name="item">The <see cref="IAdapter"/> implementation to insert.</param>
        protected override void InsertItem(int index, T item)
        {
            lock (this)
            {
                // Wire up item events and handle item initialization
                InitializeItem(item);
                base.InsertItem(index, item);
            }
        }

        /// <summary>
        /// Assigns a new element to the <see cref="Collection{T}"/> at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index for which item should be assigned.</param>
        /// <param name="item">The <see cref="IAdapter"/> implementation to assign.</param>
        protected override void SetItem(int index, T item)
        {
            lock (this)
            {
                // Dispose of existing item
                DisposeItem(this[index]);

                // Wire up item events and handle initialization of new item
                InitializeItem(item);

                base.SetItem(index, item);
            }
        }

        /// <summary>
        /// Removes the element at the specified index of the <see cref="Collection{T}"/>.
        /// </summary>
        /// <param name="index">The zero-based index of the element to remove.</param>
        protected override void RemoveItem(int index)
        {
            // Dispose of item before removing it from the collection
            lock (this)
            {
                DisposeItem(this[index]);
                base.RemoveItem(index);
            }
        }

        /// <summary>
        /// Wires events and initializes new <see cref="IAdapter"/> implementation.
        /// </summary>
        /// <param name="item">New <see cref="IAdapter"/> implementation.</param>
        /// <remarks>
        /// Derived classes should override if more events are defined.
        /// </remarks>
        protected virtual void InitializeItem(T item)
        {
            ILegacyAdapter adapter = item as ILegacyAdapter;

            if ((object)adapter == null)
                return;
            // Wire up events
            adapter.StatusMessage += item_StatusMessage;
            adapter.ProcessException += item_ProcessException;

            // Associate parent collection
            adapter.AssignParentCollection(this);

            try
            {
                // If automatically initializing new elements, handle object initialization from
                // thread pool so it can take needed amount of time
                adapter.Initialize();
                adapter.Initialized = true;
                ThreadPool.QueueUserWorkItem(state => adapter.Start());
            }
            catch (Exception ex)
            {
                // Process exception for logging
                LogDispatcher.E(DataMember, "adapter collections",
                                "Failed to queue initialize operation for adapter {0}: {1}{2}{3}", item.Info.Name,
                                ex.Message, Environment.NewLine, ex.StackTrace);
            }
        }

        /// <summary>
        /// Unwires events and disposes of <see cref="IAdapter"/> implementation.
        /// </summary>
        /// <param name="item"><see cref="IAdapter"/> to dispose.</param>
        /// <remarks>
        /// Derived classes should override if more events are defined.
        /// </remarks>
        protected virtual void DisposeItem(T item)
        {
            ILegacyAdapter adapter = item as ILegacyAdapter;

            if ((object)adapter == null)
                return;
            // Un-wire events
            adapter.StatusMessage -= item_StatusMessage;
            adapter.ProcessException -= item_ProcessException;

            // Make sure initialization handles are cleared in case any failed
            // initializations are still pending
            adapter.Initialized = true;

            // Dissociate parent collection
            adapter.AssignParentCollection(null);

            // Dipose of item, then un-wire disposed event
            adapter.Dispose();
        }

        // Raise status message event on behalf of each item in collection
        private void item_StatusMessage(object sender, EventArgs<string> e)
        {
            ILegacyAdapter adapter = sender as ILegacyAdapter;

            if ((object)adapter != null)
                LogDispatcher.Log(LogLevel.Info, adapter.Name, DataMember, e.Argument);
        }

        // Raise process exception event on behalf of each item in collection
        private void item_ProcessException(object sender, EventArgs<Exception> e)
        {
            ILegacyAdapter adapter = sender as ILegacyAdapter;
            string message = string.Format("{0}{1}{2}", e.Argument.Message, Environment.NewLine, e.Argument.StackTrace);

            if ((object)adapter != null)
                LogDispatcher.Log(LogLevel.Error, adapter.Name, DataMember, message);
        }

        #region [ Explicit IList<IAdapter> Implementation ]

        void ICollection<IAdapter>.Add(IAdapter item)
        {
            lock (this)
            {
                Add((T)item);
            }
        }

        bool ICollection<IAdapter>.Contains(IAdapter item)
        {
            lock (this)
            {
                return Contains((T)item);
            }
        }

        void ICollection<IAdapter>.CopyTo(IAdapter[] array, int arrayIndex)
        {
            lock (this)
            {
                CopyTo(array.Cast<T>().ToArray(), arrayIndex);
            }
        }

        bool ICollection<IAdapter>.Remove(IAdapter item)
        {
            lock (this)
            {
                return Remove((T)item);
            }
        }

        IEnumerator<IAdapter> IEnumerable<IAdapter>.GetEnumerator()
        {
            IAdapter[] adapters;

            lock (this)
            {
                adapters = new IAdapter[Count];

                for (int i = 0; i < Count; i++)
                    adapters[i] = this[i];
            }

            return ((IEnumerable<IAdapter>)adapters).GetEnumerator();
        }

        int IList<IAdapter>.IndexOf(IAdapter item)
        {
            lock (this)
            {
                return this.IndexOf((T)item);
            }
        }

        void IList<IAdapter>.Insert(int index, IAdapter item)
        {
            lock (this)
            {
                Insert(index, (T)item);
            }
        }

        IAdapter IList<IAdapter>.this[int index]
        {
            get
            {
                lock (this)
                {
                    return this[index];
                }
            }
            set
            {
                lock (this)
                {
                    this[index] = (T)value;
                }
            }
        }

        #endregion

        #endregion
    }
}